package com.muyu.aspect;

import com.alibaba.fastjson2.JSONObject;
import com.muyu.annotation.SysLog;
import com.muyu.body.JsonRequestHeaderParamsHelper;
import com.muyu.entity.LogInfoEntity;
import com.muyu.entity.ResponseEntity;
import com.muyu.enums.ApiStatusEnum;
import com.muyu.result.ResultResponse;
import com.muyu.service.LogInfoService;
import com.muyu.util.IpUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * @ClassName: SysLongAspect
 * @Author: Zirun.Xin
 * @Date: 2025/3/20 下午3:45
 * @Version: 1.0
 * @Description: 系统日志切面处理
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class SysLongAspect {

    /**
     * 日志信息业务层
     */
    private final LogInfoService logInfoService;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.muyu.annotation.SysLog)")
    public void logPointCut() {}

    /**
     * 环绕通知，用于拦截指定内容
     * @param pjp 切入点
     * @return 切入方法返回参数
     * @throws Throwable 最大异常
     */
    @Around("logPointCut()")
    public Object postLogAspect(ProceedingJoinPoint pjp) throws Throwable {
        // 初始化日志信息实体
        LogInfoEntity logInfoEntity = this.createOpLog();
        Object result = this.proceedController(pjp, logInfoEntity);

        // 获取日志注解信息
        SysLog sysLog = this.getSysLogAnnotation(pjp);
        logInfoEntity.setOperation(sysLog.operation());
        logInfoEntity.setType(sysLog.logType().getValue());

        // 获取返回值编码code并赋值
        ResponseEntity<?> responseData = this.getResponseData(result);
        logInfoEntity.setResponseCode(String.valueOf(responseData.getStatus()));

        // 记录请求失败异常
        if(responseData.getStatus() != ApiStatusEnum.SUCCESS.getCode()) {
            if(responseData.getBody() instanceof ResultResponse<?>) {
                ResultResponse<?> resultResponse = (ResultResponse<?>)(responseData.getBody());
                logInfoEntity.setException(resultResponse.getMessage());
            }
        }

        // 调用业务层保存日志信息实体类到数据库
        logInfoService.save(logInfoEntity);

        return result;
    }

    /**
     * 处理接口消耗时间
     * @param pjp 连接点
     * @param logInfoEntity 日志信息实体
     * @return 接口返回结果
     * @throws Throwable 异常
     */
    private Object proceedController(ProceedingJoinPoint pjp, LogInfoEntity logInfoEntity) throws Throwable {
        // 记录开始接口时间
        long spendTime = System.currentTimeMillis();

        // 调用目标接口及获取返回结果
        Object result = pjp.proceed(pjp.getArgs());

        // 调用目标接口结束，计算接口耗时
        spendTime = System.currentTimeMillis() - spendTime;

        // 给日志信息实体消耗时间赋值
        logInfoEntity.setSpendTime(spendTime);

        return result;
    }

    /**
     * 获取接口返回值
     * @param result 接口返回结果
     * @return
     */
    private ResponseEntity<?> getResponseData(Object result) {
        // 判断返回体类型是否为ResponseEntity<?>
        if(result instanceof ResponseEntity<?>) {
            return (ResponseEntity<?>) result;
        }

        return ResponseEntity.ok(result);
    }

    /**
     * 获取请求体
     * @param request 请求对象
     */
    private String getBody(HttpServletRequest request) {
        // 如果请求类型为json入参类型
        if(MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())) {
            try {
                JSONObject jsonObject = JsonRequestHeaderParamsHelper.parseHeader(request);
                return jsonObject.toJSONString();
            } catch (IOException e) {
                log.error("请求体数据读取异常，异常信息：{}", e.getMessage());
            }
        }

        return null;
    }

    /**
     * 获取请求IP地址
     * @return IP地址
     */
    private String getIpAddress() {
        // 通过 RequestContextHolder 获取当前请求的 HttpServletRequest 对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();

            try {
                return IpUtils.getIpAddr(request);
            } catch (Exception e) {
                log.error("无法获取IP地址，异常信息：{}", e.getMessage());
            }
        }
        return null;
    }

    /**
     * 构建日志对象
     * @return 日志信息实体
     */
    private LogInfoEntity createOpLog() {
        HttpServletRequest request = this.getRequest();
        LogInfoEntity logInfoEntity = new LogInfoEntity();
        logInfoEntity.setQuery(request.getQueryString());
        logInfoEntity.setBody(this.getBody(this.getRequest()));
        logInfoEntity.setOperateTime(new Date());
        logInfoEntity.setUrl(request.getServletPath());
        logInfoEntity.setIp(this.getIpAddress());
        return logInfoEntity;
    }

    /**
     * 获取请求对象
     * @return 请求对象
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        return request;
    }

    /**
     * 获取日志注解信息
     * @param pjp 连接点
     * @return 日志注解信息
     */
    private SysLog getSysLogAnnotation(ProceedingJoinPoint pjp) {
        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) pjp.getSignature();

        //获取切入点所在的方法
        Method method = signature.getMethod();

        //获取注解信息
        return method.getAnnotation(SysLog.class);
    }
}
